﻿
#region Header
/********************************************************           
 * ResponseMessage      
 *                                                         
 * Author:  Richard Barclay  (phphanatic at gmail dot com) 
 * 
 * Copyright: 2009 Richard Barclay
 * 
 * License: GNU General Public License (GPL)
 *     
 * This file is part of the Intergr8.GoogleVisualization library.
 *
 *   The Intergr8.GoogleVisualization library is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *  Intergr8.GoogleVisualization library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with Intergr8.GoogleVisualization library.  If not, see <http://www.gnu.org/licenses/>.
 *                                                         
 * Purpose:  Pull together all components of the response and build the final JSON structure
 *                                                         
 * Usage:                                                  
 *      Initialise with the original RequestHeader and corresponding ResponseData objects
 *      Output from toJSON contains a valid Google Visualization API v0.6 message
 ********************************************************/
#endregion


namespace Intergr8.GoogleVisualization
{

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Intergr8.GoogleVisualization.Interfaces;



    /// <summary>
    /// This class contains the attributes and data that will be packaged up in a response.
    /// 
    /// The ToString is overloaded on this class to generate the JSON equivalent of the attributes
    /// </summary>
    public class ResponseMessage : IResponseMessage
    {

        #region Static properties
        private static string STATUS_OK = "ok";
        private static string STATUS_WARNING = "warning";
        private static string STATUS_ERROR = "error";
        private static string DEFAULT_ERROR_MSG = "An internal error occurred while generating the response.";
        #endregion

        #region Response message attributes
        private IRequestHandler _request = null;
        private IResponseData _data = null;

        private StatusMessageCollection _errors = new StatusMessageCollection();
        private StatusMessageCollection _warnings = new StatusMessageCollection();
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor is private to control creation
        /// </summary>
        private ResponseMessage()
        {
        }

        /// <summary>
        /// Parameterised constructor to generate a response from an original request. No data is returned
        /// </summary>
        /// <param name="request"></param>
        private ResponseMessage(IRequestHandler request)
        {
            // Always initialise, this ensures that any queries in the request TQ are applied to the data
            Init(request,null);
        }

        /// <summary>
        /// Create a ResponseMessage from an orignal Request and the appropriate response data
        /// </summary>
        /// <param name="request">RequestHeader object</param>
        /// <param name="data">ResponseData object</param>
        private ResponseMessage(IRequestHandler request, IResponseData data)
        {
            // Always initialise, this ensures that any queries in the TQ are applied to the data
            Init(request, data);
        }

        private ResponseMessage(IRequestHandler request, Exception ex)
        {
            Init(request, null);
            _errors.Add(new StatusMessage("An internal error has occurred",ex.Message));
        }

        #endregion

        #region Factory Methods
        /// <summary>
        /// Create an IResponseMessage interface from the given IRequestHandler
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public static IResponseMessage CreateResponse(IRequestHandler request)
        {
            IResponseMessage resp = new ResponseMessage(request);
            return resp;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static IResponseMessage CreateResponse(IRequestHandler request, IResponseData data)
        {
            IResponseMessage resp = new ResponseMessage(request, data);
            return resp;
        }


        public static IResponseMessage CreateResponse(IRequestHandler request, Exception ex)
        {
            IResponseMessage resp = new ResponseMessage(request, ex);
            
            return resp;
        }

        #endregion



        #region Public Properties
        /// <summary>
        /// Returns the signature of the underlying data.  
        /// </summary>
        public string Sig
        {
            get { return _data.GetHashCode().ToString(); }
        }

        /// <summary>
        /// 
        /// </summary>
        public IResponseData Data
        {
            get { return _data; }
        }

        /// <summary>
        /// 
        /// </summary>
        public IRequestHandler MetaData
        {
            get { return _request; }
        }

        /// <summary>
        /// Return the current status of the ResponseMessage based on the internal list of Errors and Warnings
        /// </summary>
        public string Status
        {
            get {
                if(_errors.HasValues)
                {
                    return STATUS_ERROR;
                }
                if (_warnings.HasValues)
                {
                    return STATUS_WARNING;
                }
                return STATUS_OK;

            }
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Output the data in the requested format
        /// </summary>
        /// <returns></returns>
        public string Output()
        {
            switch (_request.TQX.Output)
            {
                case ITQXFormatting.outputFormatEnum.json:
                    return this.ToJSON();
                    break;
                case ITQXFormatting.outputFormatEnum.html:
                    return this.toHTML();
                    break;
                default:                  // Default format to JSON
                    return this.ToJSON();
                    break;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public IStatusMessageCollection Errors
        {
            get
            {
                // If the response is OK or in WARNING status then were not bothered about Errors
                if ((this.Status == STATUS_OK)||(this.Status == STATUS_WARNING))
                {
                    return StatusMessageCollection.Empty();
                }
                else
                {
                    return _errors;
                }
            }
        }

        /// <summary>
        /// Return the Warning message collection.
        /// 
        /// The warnings will only be returned if the object is in the WARNING  state
        /// </summary>
        public IStatusMessageCollection Warnings
        {
            get {
                // If the response is OK or ERROR status then were not bothered about Warnings
                if ((this.Status == STATUS_OK)||(this.Status == STATUS_ERROR))
                {
                    return StatusMessageCollection.Empty();
                }
                else
                {
                    return _warnings;
                }
            }
        }

        #endregion

        #region Private
        /// <summary>
        /// Initialise from the constructors
        /// </summary>
        /// <param name="request">RequestHeader object</param>
        /// <param name="data">ResponseData object</param>
        private void Init(IRequestHandler request, IResponseData data)
        {
            if (request == null)
            {
                _errors.Add(ErrorMessage.InvalidRequest("No request information has been provided", "The RequestHandler object is null"));
            }

            if (request.IsValid == false)
            {
                //TODO: Implement the ability to output errors from an object for logging purposes
                _errors.Add(ErrorMessage.InvalidRequest("An invalid request has been made", "The RequestHandler is Invalid"));
            }

            _request = request;

            _data = data;

            // apply any request TQ query to the data
            try
            {
                _data.Filter(request.Query);
            }
            catch (Exception ex)
            {
                _errors.Add(ErrorMessage.InvalidQuery("An error has occurred while processing the requested query.", ex.Message));
            }
        }

        /// <summary>
        /// Identify if the contained data has changed since the request came in.
        /// 
        /// To do this, we compare Hash values of the client Requuest.Sig and the RepsonseData.HasCode
        /// </summary>
        private bool DataChanged
        {
            get
            {
                bool bRet = true;

                // No data to compare too
                if (_data == null)
                {
                    bRet = false;
                    _warnings.Add(WarningMessage.Other("No data is available to generate a hash", "The requested data cannot be retreived and a hash signature has not been generated. The data content is assumed unchanged"));
                }
                else
                {
                    int clientHash = _request.TQX.Sig;
                    int dataHash = _data.GetHashCode();

                    if (clientHash == dataHash)
                    {
                        bRet = false;
                    }
                }

                return bRet;
            }
        }

        /// <summary>
        /// Returns the HTML formatted data and schema for debugging
        /// </summary>
        /// <returns></returns>
        private string toHTML()
        {
            StringBuilder response = new StringBuilder();
            response.Append(_data.toXml);
            return response.ToString();
        }


        /// <summary>
        /// Returns the JSON structure of the Response message class
        /// </summary>
        /// <returns></returns>
        private string ToJSON()
        {
            // Force a data conversion first to catch any issues
            // if an exception is thrown, then the ResponseMessage is marked as "error"
            // before it is built up
            string formatted_data = "";
            try
            {
                formatted_data = _data.toJSON;
            }
            catch (Exception ex)
            {
                _errors.Add(ErrorMessage.InternalError("An issue occurred during the conversion on data to the JSON  format.", ex.Message));

            }
            StringBuilder response = new StringBuilder();

            // Append the Version info, as requested from the Client
            response.Append(String.Format("\"version\":\"{0}\"", _request.TQX.Version));

            // Append the Request ID as sent by the client
            response.Append(String.Format(",\"reqId\":\"{0}\"", _request.TQX.ReqId));

            // Check for any changed data content
            if (DataChanged)
            {
                // Return the new data signature
                response.Append(String.Format(",\"sig\":\"{0}\"", _data.GetHashCode()));
            }
            else
            {
                // The data is unchanged
                // Add a error message flagging that no Data is in the message
                _errors.Add(ErrorMessage.NotModified("", ""));

                // The sig is the old sig value passed back by the client
                response.Append(String.Format(",\"sig\":\"{0}\"", _request.TQX.Sig));
            }

            // Add the Status to the response message
            response.Append(String.Format(",\"status\":\"{0}\"", Status));

            // Add STATUS_OK message content
            if (Status == STATUS_OK)
            {
                response.Append(String.Format(",\"table\":{0}", formatted_data));
            }


            // Add STATUS_WARNING message content
            if (Status == STATUS_WARNING)
            {
                response.Append(String.Format(",\"warnings\":\"[{0}]\"", _warnings.toJSON()));
                response.Append(String.Format(",\"table\":{0}", formatted_data));
            }

            // Add STATUS_ERROR message content
            if (Status == STATUS_ERROR)
            {
                response.Append(String.Format(",\"errors\":\"[{0}]\"", _errors.toJSON()));
            }

            // wrap up the message with the requests Handler function
            string handler = String.Format("{0}({{{1}}})", _request.TQX.ResponseHandler, response.ToString());

            return handler;
        }

        #endregion




    }
}
